package com.hunter.lucene.util.xml.parse;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.lucene.document.Field.Index;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.Field.TermVector;

import com.hunter.lucene.util.annotation.LoadStrategy;
import com.hunter.lucene.util.annotation.parse.DocumentAnnotationParser;
import com.hunter.lucene.util.config.DiscriminatorValue;
import com.hunter.lucene.util.config.DocumentConfigInfo;
import com.hunter.lucene.util.config.FieldableConfigInfo;
import com.hunter.lucene.util.config.UndocumentConfigInfo;
import com.hunter.lucene.util.config.UnfieldableConfigInfo;
import com.hunter.lucene.util.convert.FieldConvertor;
import com.hunter.lucene.util.convert.UnfieldConvertor;
import com.hunter.lucene.util.xml.DocumentXmlConfig;
import com.hunter.lucene.util.xml.DocumentXmlConfigAdapter;
import com.hunter.lucene.util.xml.FieldXmlConfigAdapter;
import com.hunter.lucene.util.xml.FieldableXmlConfig;
import com.hunter.lucene.util.xml.UndocumentXmlConfig;
import com.hunter.lucene.util.xml.UndocumentXmlConfigAdapter;
import com.hunter.lucene.util.xml.UnfieldXmlConfigAdapter;
import com.hunter.lucene.util.xml.UnfieldableXmlConfig;
import com.hunter.schema.DiscriminatorType;
import com.hunter.schema.SearchBeanFieldType;
import com.hunter.schema.SearchBeanMethodType;
import com.hunter.schema.SearchBeanPropertyType;
import com.hunter.schema.SearchBeanType;
import com.hunter.schema.WriteBeanFieldType;
import com.hunter.schema.WriteBeanMethodType;
import com.hunter.schema.WriteBeanPropertyType;
import com.hunter.schema.WriteBeanType;

/**
 * 
 * @author bastengao
 * 
 */
public class XmlSchemaConfigParser {

	/**
	 * 解析 xml 由 jaxb 返回的信息。并这些信息转化为 DocumentConfigInf。
	 * 
	 * @param writeBean
	 * @return
	 * @throws ClassNotFoundException
	 * @throws IntrospectionException
	 * @throws NoSuchMethodException
	 * @throws SecurityException
	 */
	public DocumentConfigInfo parse(WriteBeanType writeBean)
			throws ClassNotFoundException, IntrospectionException,
			SecurityException, NoSuchMethodException {
		if (writeBean == null) {
			throw new IllegalArgumentException(new NullPointerException(
					"writeBean is null"));
		}
		DocumentXmlConfig documentConfig = new DocumentXmlConfig();

		String clazz = writeBean.getClazz();
		if (clazz == null) {
			throw new NullPointerException("the class of writeBean is null");
		} else {
			if (clazz.equals("")) {
				throw new IllegalArgumentException(
						"the clazz of writeBean is empty");
			}
		}
		Class<?> classType = Class.forName(clazz);
		documentConfig.setDocumentedClass(classType);

		DiscriminatorType disc = writeBean.getDiscriminator();
		if (disc == null) {
			throw new NullPointerException(
					"the discriminator of writeBean is null");
		} else {
			if (disc.getName() == null) {
				throw new NullPointerException(
						"the name of discriminator of writeBean is null");
			} else {
				if (disc.getName().equals("")) {
					throw new IllegalArgumentException(
							"the name of discriminator of writeBean is empty");
				}
			}

			if (disc.getValue() == null) {
				throw new NullPointerException(
						"the name of discriminator of writeBean is null");
			} else {
				if (disc.getValue().equals("")) {
					throw new IllegalArgumentException(
							"the name of discriminator of writeBean is empty");
				}
			}
		}
		documentConfig.setDiscriminator(new DiscriminatorValue(disc.getName(),
				disc.getValue()));

		String analyzerName = writeBean.getAnalyzer();
		if (analyzerName == null) {
			throw new NullPointerException(
					"the analyzer name of writeBean is null");
		} else {
			if (analyzerName.equals("")) {
				throw new IllegalArgumentException(
						"the analyzer name of writeBean is empty");
			}
		}
		documentConfig.setAnalyzer(analyzerName);
		documentConfig.setBoost(writeBean.getBoost());
		documentConfig.setFieldConfigs(new ArrayList<FieldableConfigInfo>());

		stuff(writeBean, classType, documentConfig);

		DocumentConfigInfo documentConfigInfo = new DocumentXmlConfigAdapter(
				documentConfig);
		return documentConfigInfo;
	}

	private void stuff(WriteBeanType writeBean, Class<?> beanclazz,
			DocumentXmlConfig documentConfig) throws IntrospectionException,
			SecurityException, NoSuchMethodException, ClassNotFoundException {
		String uniqueFieldId = writeBean.getUniqueField();
		parseProperty(writeBean.getProperty(), beanclazz, documentConfig,
				uniqueFieldId);
		parseMethod(writeBean.getMethod(), beanclazz, documentConfig,
				uniqueFieldId);
	}

	private void parseProperty(List<WriteBeanPropertyType> properties,
			Class<?> bean, DocumentXmlConfig documentConfig,
			String uniqueFieldId) throws IntrospectionException,
			ClassNotFoundException {
		Map<String, PropertyDescriptor> beanProperties = DocumentAnnotationParser
				.getPropertyMap(bean);

		for (WriteBeanPropertyType property : properties) {

			String propertyName = property.getName();
			if (propertyName == null) {
				throw new NullPointerException("the name of property is null");
			} else {
				if (propertyName.equals("")) {
					throw new IllegalArgumentException(
							"the name of property is empty");
				}
			}

			PropertyDescriptor beanProperty = beanProperties.get(propertyName);

			if (beanProperty == null) {
				throw new IllegalArgumentException("no this property: \""
						+ propertyName + "\" in class:" + bean.getName());
			}

			Method readMethod = beanProperty.getReadMethod();

			if (readMethod == null) {
				throw new IllegalArgumentException("the property \""
						+ propertyName + "\" of class \"" + bean.getName()
						+ "\" has not a read method");
			}
			for (WriteBeanFieldType field : property.getField()) {
				parseWriteBeanField(field, readMethod, documentConfig,
						uniqueFieldId);
			}
		}
	}

	private void parseMethod(List<WriteBeanMethodType> methods, Class<?> bean,
			DocumentXmlConfig documentConfig, String uniqueFieldId)
			throws SecurityException, NoSuchMethodException,
			ClassNotFoundException {
		for (WriteBeanMethodType method : methods) {
			String methodName = method.getName();
			if (methodName == null) {
				throw new NullPointerException("the name of method is null");
			} else {
				if (methodName.equals("")) {
					throw new IllegalArgumentException(
							"the name of method is empty");
				}
			}

			Method readMethod = bean.getMethod(methodName);
			if (readMethod == null) {
				throw new IllegalArgumentException(
						"the method of class is not available");
			}

			for (WriteBeanFieldType field : method.getField()) {
				parseWriteBeanField(field, readMethod, documentConfig,
						uniqueFieldId);
			}
		}
	}

	private void parseWriteBeanField(WriteBeanFieldType field,
			Method readMethod, DocumentXmlConfig documentConfig,
			String uniqueFieldId) throws ClassNotFoundException {
		FieldableXmlConfig config = new FieldableXmlConfig();

		String fieldName = field.getName();
		if (fieldName == null) {
			throw new NullPointerException("the name of field is null");
		} else {
			if (fieldName.equals("")) {
				throw new IllegalArgumentException("the name of field is empty");
			}
		}

		String convertorClassName = field.getConvertor();
		if (convertorClassName == null) {
			throw new NullPointerException(
					"the convertor class name of field is null");
		} else {
			if (convertorClassName.equals("")) {
				throw new IllegalArgumentException(
						"the convertor class name of field is empty");
			}
		}

		Class<?> convertorClass = Class.forName(convertorClassName);
		Class<? extends FieldConvertor> convertor = convertorClass
				.asSubclass(FieldConvertor.class);
		config.setName(fieldName);
		config.setBoost(field.getBoost());
		config.setStore(Store.valueOf(field.getStore().name()));
		config.setIndex(Index.valueOf(field.getIndex().name()));
		config.setTermVector(TermVector.valueOf(field.getTermVector().name()));
		config.setAttributeType(readMethod.getReturnType());
		config.setReadMethod(readMethod);
		config.setConvertor(convertor);

		FieldableConfigInfo fieldConfig = new FieldXmlConfigAdapter(config);
		documentConfig.getFieldConfigs().add(fieldConfig);

		String fieldId = field.getId();
		if (fieldId != null) {
			if (fieldId.equals(uniqueFieldId)) {
				documentConfig.setUniqueField(fieldConfig);
			}
		}
	}

	public UndocumentConfigInfo parse(SearchBeanType searchBean)
			throws ClassNotFoundException, IntrospectionException, Exception {
		UndocumentXmlConfig undocumentConfig = new UndocumentXmlConfig();

		String className = searchBean.getClazz();
		if (className == null) {
			throw new NullPointerException(
					"the class name of searchBean is null");
		} else {
			if (className.equals("")) {
				throw new IllegalArgumentException(
						"the class name of searchBean is empty");
			}
		}
		Class<?> searchBeanClass = Class.forName(className);
		undocumentConfig.setUndocumentedClass(searchBeanClass);

		DiscriminatorType disc = searchBean.getDiscriminator();
		if (disc == null) {
			throw new NullPointerException(
					"the discriminator of searchBean is null");
		} else {
			if (disc.getName() == null) {
				throw new NullPointerException(
						"the name of discriminator of searchBean is null");
			} else {
				if (disc.getName().equals("")) {
					throw new IllegalArgumentException(
							"the name of discriminator of searchBean is empty");
				}
			}

			if (disc.getValue() == null) {
				throw new NullPointerException(
						"the name of discriminator of searchBean is null");
			} else {
				if (disc.getValue().equals("")) {
					throw new IllegalArgumentException(
							"the name of discriminator of searchBean is empty");
				}
			}
		}

		DiscriminatorValue discriminator = new DiscriminatorValue(disc
				.getName(), disc.getValue());
		undocumentConfig.setDiscrimination(discriminator);

		undocumentConfig
				.setUnfieldableConfigs(new ArrayList<UnfieldableConfigInfo>());
		stuff(searchBean, searchBeanClass, undocumentConfig);

		return new UndocumentXmlConfigAdapter(undocumentConfig);
	}

	private void stuff(SearchBeanType searchBean, Class<?> beanClass,
			UndocumentXmlConfig undocumentConfig)
			throws IntrospectionException, ClassNotFoundException,
			SecurityException, NoSuchMethodException {
		for (SearchBeanFieldType field : searchBean.getField()) {
			UnfieldableXmlConfig unfieldConfig = new UnfieldableXmlConfig();

			String fieldName = field.getName();
			if (fieldName == null) {
				throw new NullPointerException("the name of field is null");
			} else {
				if (fieldName.equals("")) {
					throw new IllegalArgumentException(
							"the name of field is empty");
				}
			}
			unfieldConfig.setName(fieldName);
			unfieldConfig.setLoadStrategy(LoadStrategy.valueOf(field.getLoad()
					.name()));
			parseSearchBeanProperty(field.getProperty(), beanClass,
					unfieldConfig, undocumentConfig);
			parseSearchBeanMethod(field.getMethod(), beanClass, unfieldConfig,
					undocumentConfig);
		}
	}

	private void parseSearchBeanProperty(
			List<SearchBeanPropertyType> properties, Class<?> beanClass,
			UnfieldableXmlConfig unfieldConfig,
			UndocumentXmlConfig undocumentConfig)
			throws IntrospectionException, ClassNotFoundException {
		Map<String, PropertyDescriptor> beanProperties = DocumentAnnotationParser
				.getPropertyMap(beanClass);

		for (SearchBeanPropertyType property : properties) {
			String propertyName = property.getName();
			if (propertyName == null) {
				throw new NullPointerException("the name of property is null");
			} else {
				if (propertyName.equals("")) {
					throw new IllegalArgumentException(
							"the name of property is empty");
				}
			}
			PropertyDescriptor pd = beanProperties.get(propertyName);
			if (pd == null) {
				throw new IllegalArgumentException("no this property: \""
						+ propertyName + "\" in class:" + beanClass.getName());
			}
			Method writeMethod = pd.getWriteMethod();
			if (writeMethod == null) {
				throw new IllegalArgumentException("");
			}
			unfieldConfig.setWriteMethod(writeMethod);
			unfieldConfig.setAttributeClass(pd.getPropertyType());

			String convertorClassName = property.getConvertor();
			if (convertorClassName == null) {
				throw new NullPointerException(
						"the convertor of property is null");
			} else {
				if (convertorClassName.equals("")) {
					throw new IllegalArgumentException(
							"the convertor of property is empty");
				}
			}
			Class<?> convertorClass = Class.forName(convertorClassName);
			Class<? extends UnfieldConvertor> convertor = convertorClass
					.asSubclass(UnfieldConvertor.class);
			unfieldConfig.setConvertor(convertor);
			UnfieldXmlConfigAdapter unfieldConfigInfo = new UnfieldXmlConfigAdapter(
					unfieldConfig);
			undocumentConfig.getUnfieldableConfigs().add(unfieldConfigInfo);
		}
	}

	private void parseSearchBeanMethod(List<SearchBeanMethodType> methods,
			Class<?> beanClass, UnfieldableXmlConfig unfieldConfig,
			UndocumentXmlConfig undocumentConfig)
			throws ClassNotFoundException, SecurityException,
			NoSuchMethodException {
		for (SearchBeanMethodType method : methods) {
			String methodName = method.getName();
			if (methodName == null) {
				throw new NullPointerException("the name of method is null");
			} else {
				if (methodName.equals("")) {
					throw new IllegalArgumentException(
							"the name of method is empty");
				}
			}

			String paramType = method.getParamType();
			if (paramType == null) {
				throw new NullPointerException("");
			} else {
				if (paramType == null) {
					throw new IllegalArgumentException();
				}
			}
			Class<?> param = Class.forName(paramType);

			Method writeMethod = beanClass.getMethod(methodName, param);

			unfieldConfig.setWriteMethod(writeMethod);
			unfieldConfig.setAttributeClass(param);

			String convertorClassName = method.getConvertor();
			if (convertorClassName == null) {
				throw new NullPointerException(
						"the convertor of method is null");
			} else {
				if (convertorClassName.equals("")) {
					throw new IllegalArgumentException(
							"the convertor of method is empty");
				}
			}
			Class<?> convertorClass = Class.forName(convertorClassName);
			Class<? extends UnfieldConvertor> convertor = convertorClass
					.asSubclass(UnfieldConvertor.class);
			unfieldConfig.setConvertor(convertor);

			UnfieldXmlConfigAdapter unfieldConfigInfo = new UnfieldXmlConfigAdapter(
					unfieldConfig);
			undocumentConfig.getUnfieldableConfigs().add(unfieldConfigInfo);
		}

	}

}
